import React, {Component} from 'react';
import {
    Text,
    View,
    Dimensions,
    Animated,
    ViewPropTypes,
    TouchableOpacity, Platform
} from 'react-native';
import PropTypes from 'prop-types';
import XDate from 'xdate';

import {parseDate, xdateToData} from '../interface';
import dateutils from '../dateutils';
import CalendarList from '../calendar-list';
import ReservationsList from './reservation-list';
import styleConstructor from './style';
import { VelocityTracker } from '../input';

const HEADER_HEIGHT = 104;
const KNOB_HEIGHT = 24;

//Fallback when RN version is < 0.44
const viewPropTypes = ViewPropTypes || View.propTypes;

export default class AgendaView extends Component {
    static propTypes = {
        // Specify theme properties to override specific styles for calendar parts. Default = {}
        theme: PropTypes.object,

        // agenda container style
        style: viewPropTypes.style,

        // the list of items that have to be displayed in agenda. If you want to render item as empty date
        // the value of date key has to be an empty array []. If there exists no value for date key it is
        // considered that the date in question is not yet loaded
        items: PropTypes.object,

        // callback that gets called when items for a certain month should be loaded (month became visible)
        loadItemsForMonth: PropTypes.func,
        // callback that fires when the calendar is opened or closed
        onCalendarToggled: PropTypes.func,
        // callback that gets called on day press
        onDayPress: PropTypes.func,
        // callback that gets called when day changes while scrolling agenda list
        onDaychange: PropTypes.func,
        // specify how each item should be rendered in agenda
        renderItem: PropTypes.func,
        // specify how each date should be rendered. day can be undefined if the item is not first in that day.
        renderDay: PropTypes.func,
        // specify how agenda knob should look like
        renderKnob: PropTypes.func,
        renderKnobUp: PropTypes.func,
        // specify how empty date content with no items should be rendered
        renderEmptyDay: PropTypes.func,
        // specify what should be rendered instead of ActivityIndicator
        renderEmptyData: PropTypes.func,
        // specify your item comparison function for increased performance
        rowHasChanged: PropTypes.func,

        // Max amount of months allowed to scroll to the past. Default = 50
        pastScrollRange: PropTypes.number,

        // Max amount of months allowed to scroll to the future. Default = 50
        futureScrollRange: PropTypes.number,

        // initially selected day
        selected: PropTypes.any,
        // Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined
        minDate: PropTypes.any,
        // Maximum date that can be selected, dates after maxDate will be grayed out. Default = undefined
        maxDate: PropTypes.any,

        // If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday.
        firstDay: PropTypes.number,

        // Collection of dates that have to be marked. Default = items
        markedDates: PropTypes.object,
        // Optional marking type if custom markedDates are provided
        markingType: PropTypes.string,

        // Hide knob button. Default = false
        hideKnob: PropTypes.bool,
        // Month format in calendar title. Formatting values: http://arshaw.com/xdate/#Formatting
        monthFormat: PropTypes.string,
        // A RefreshControl component, used to provide pull-to-refresh functionality for the ScrollView.
        refreshControl: PropTypes.element,
        // If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make sure to also set the refreshing prop correctly.
        onRefresh: PropTypes.func,
        // Set this true while waiting for new data from a refresh.
        refreshing: PropTypes.bool,
        // Display loading indicador. Default = false
        displayLoadingIndicator: PropTypes.bool,


        dragStart: PropTypes.func,

    };


    constructor(props) {
        super(props);
        this.styles = styleConstructor(props.theme);
        const windowSize = Dimensions.get('window');
        this.viewHeight = windowSize.height;
        this.viewWidth = windowSize.width;
        this.scrollTimeout = undefined;
        this.headerState = 'idle';
        this.state = {
            scrollY: new Animated.Value(0),
            calendarIsReady: false,
            calendarScrollable: false,
            firstResevationLoad: false,
            selectedDay: parseDate(this.props.selected) || XDate(true),
            topDay: parseDate(this.props.selected) || XDate(true),
        };
        this.currentMonth = this.state.selectedDay.clone();
        this.onLayout = this.onLayout.bind(this);
        this.onScrollPadLayout = this.onScrollPadLayout.bind(this);
        this.onTouchStart = this.onTouchStart.bind(this);
        this.onTouchEnd = this.onTouchEnd.bind(this);
        this.onStartDrag = this.onStartDrag.bind(this);
        this.onSnapAfterDrag = this.onSnapAfterDrag.bind(this);
        this.generateMarkings = this.generateMarkings.bind(this);
        this.knobTracker = new VelocityTracker();
        this.state.scrollY.addListener(({value}) => this.knobTracker.add(value));
    }

    closeCalendar(){

        //todo
        let day = this.state.selectedDay;

        let optimisticScroll = !this.state.calendarScrollable;
        this.setState({
            calendarScrollable: false,
            selectedDay: day.clone()
        });
        if (this.props.onCalendarToggled) {
            this.props.onCalendarToggled(false);
        }
        if (!optimisticScroll) {
            this.setState({
                topDay: day.clone()
            });
        }
        this.setScrollPadPosition(this.initialScrollPadPosition(), true);
        this.calendar.scrollToDay(day, this.calendarOffset(), true);
        if (this.props.loadItemsForMonth) {
            this.props.loadItemsForMonth(xdateToData(day));
        }
        if (this.props.onDayPress) {
            this.props.onDayPress(xdateToData(day));
        }

    }

    calendarOffset() {
        return 90 - (this.viewHeight / 2);
    }

    initialScrollPadPosition() {
        return Math.max(0, this.viewHeight - HEADER_HEIGHT);
    }

    setScrollPadPosition(y, animated) {
        this.scrollPad._component.scrollTo({x: 0, y, animated});
    }

    onScrollPadLayout() {
        // When user touches knob, the actual component that receives touch events is a ScrollView.
        // It needs to be scrolled to the bottom, so that when user moves finger downwards,
        // scroll position actually changes (it would stay at 0, when scrolled to the top).
        this.setScrollPadPosition(this.initialScrollPadPosition(), false);
        // delay rendering calendar in full height because otherwise it still flickers sometimes
        setTimeout(() => this.setState({calendarIsReady: true}), 0);
    }

    onLayout(event) {
        this.viewHeight = event.nativeEvent.layout.height;
        this.viewWidth = event.nativeEvent.layout.width;
        this.forceUpdate();
    }

    onTouchStart() {
        this.headerState = 'touched';
        if (this.knob) {
            this.knob.setNativeProps({style: { opacity: 0.5 }});
        }
    }

    onTouchEnd() {
        if (this.knob) {
            this.knob.setNativeProps({style: { opacity: 1 }});
        }

        if (this.headerState === 'touched') {
            this.setScrollPadPosition(0, true);
            this.enableCalendarScrolling();
        }
        this.headerState = 'idle';
    }

    onStartDrag() {
        this.headerState = 'dragged';
        this.knobTracker.reset();
        this.props.dragStart(true);
    }

    onSnapAfterDrag(e) {
        // on Android onTouchEnd is not called if dragging was started
        this.onTouchEnd();
        const currentY = e.nativeEvent.contentOffset.y;
        this.knobTracker.add(currentY);
        const projectedY = currentY + this.knobTracker.estimateSpeed() * 250/*ms*/;
        const maxY = this.initialScrollPadPosition();
        const snapY = (projectedY > maxY / 2) ? maxY : 0;
        this.setScrollPadPosition(snapY, true);
        if (snapY === 0) {
            this.enableCalendarScrolling();
        }else{
            this.props.dragStart(false);
        }
    }

    onVisibleMonthsChange(months) {
        if (this.props.items && !this.state.firstResevationLoad) {
            clearTimeout(this.scrollTimeout);
            this.scrollTimeout = setTimeout(() => {
                if (this.props.loadItemsForMonth && this._isMounted) {
                    this.props.loadItemsForMonth(months[0]);
                }
            }, 200);
        }
    }

    loadReservations(props) {
        if ((!props.items || !Object.keys(props.items).length) && !this.state.firstResevationLoad) {
            this.setState({
                firstResevationLoad: true
            }, () => {
                if (this.props.loadItemsForMonth) {
                    this.props.loadItemsForMonth(xdateToData(this.state.selectedDay));
                }
            });
        }
    }

    componentWillMount() {
        this._isMounted = true;
        this.loadReservations(this.props);
    }

    componentWillUnmount() {
        this._isMounted = false;
    }

    componentWillReceiveProps(props) {
        if (props.items) {
            this.setState({
                firstResevationLoad: false
            });
        } else {
            this.loadReservations(props);
        }
    }

    enableCalendarScrolling() {
        this.setState({
            calendarScrollable: true
        });
        if (this.props.onCalendarToggled) {
            this.props.onCalendarToggled(true);
        }
        // Enlarge calendarOffset here as a workaround on iOS to force repaint.
        // Otherwise the month after current one or before current one remains invisible.
        // The problem is caused by overflow: 'hidden' style, which we need for dragging
        // to be performant.
        // Another working solution for this bug would be to set removeClippedSubviews={false}
        // in CalendarList listView, but that might impact performance when scrolling
        // month list in expanded CalendarList.
        // Further info https://github.com/facebook/react-native/issues/1831
        this.calendar.scrollToDay(this.state.selectedDay, this.calendarOffset() + 1, true);
    }

    _chooseDayFromCalendar(d) {
        this.chooseDay(d, !this.state.calendarScrollable);
    }

    chooseDay(d, optimisticScroll) {
        const day = parseDate(d);
        if (this.props.onCalendarToggled) {
            this.props.onCalendarToggled(false, this.state.calendarScrollable);
        }
        this.setState({
            calendarScrollable: false,
            selectedDay: day.clone()
        });
        if (!optimisticScroll) {
            this.setState({
                topDay: day.clone()
            });
        }
        this.setScrollPadPosition(this.initialScrollPadPosition(), true);
        this.calendar.scrollToDay(day, this.calendarOffset(), true);
        if (this.props.loadItemsForMonth) {
            this.props.loadItemsForMonth(xdateToData(day));
        }
        if (this.props.onDayPress) {
            this.props.onDayPress(xdateToData(day));
        }
    }

    renderReservations() {
        return (
            <ReservationsList
                refreshControl={this.props.refreshControl}
                refreshing={this.props.refreshing}
                onRefresh={this.props.onRefresh}
                rowHasChanged={this.props.rowHasChanged}
                renderItem={this.props.renderItem}
                renderDay={this.props.renderDay}
                renderEmptyDate={this.props.renderEmptyDate}
                reservations={this.props.items}
                selectedDay={this.state.selectedDay}
                renderEmptyData={this.props.renderEmptyData}
                topDay={this.state.topDay}
                onDayChange={this.onDayChange.bind(this)}
                onScroll={() => {}}
                ref={(c) => this.list = c}
                theme={this.props.theme}
            />
        );
    }

    onDayChange(day) {
        const newDate = parseDate(day);
        const withAnimation = dateutils.sameMonth(newDate, this.state.selectedDay);
        this.calendar.scrollToDay(day, this.calendarOffset(), withAnimation);
        this.setState({
            selectedDay: parseDate(day)
        });

        if (this.props.onDayChange) {
            this.props.onDayChange(xdateToData(newDate));
        }
    }

    generateMarkings() {
        let markings = this.props.markedDates;
        if (!markings) {
            markings = {};
            Object.keys(this.props.items  || {}).forEach(key => {
                if (this.props.items[key] && this.props.items[key].length) {
                    markings[key] = {marked: true};
                }
            });
        }
        const key = this.state.selectedDay.toString('yyyy-MM-dd');
        return {...markings, [key]: {...(markings[key] || {}), ...{selected: true}}};
    }

    isIphoneX() {
        let X_WIDTH = 375;
        let X_HEIGHT = 812;
        let screenW = Dimensions.get('window').width;
        let screenH = Dimensions.get('window').height;


        let XSMAX_WIDTH = 414;
        let XSMAX_HEIGHT = 896;


        return (
            Platform.OS === 'ios' &&
            ((screenH === X_HEIGHT && screenW === X_WIDTH) ||
                (screenH === X_WIDTH && screenW === X_HEIGHT) || (screenH === XSMAX_HEIGHT && screenW === XSMAX_WIDTH) || (screenH === XSMAX_WIDTH && screenW === XSMAX_HEIGHT))
        )
    }

    render() {
        const agendaHeight = Math.max(0, this.viewHeight - HEADER_HEIGHT);
        const weekDaysNames = dateutils.weekDayNames(this.props.firstDay);
        const weekdaysStyle = [this.styles.weekdays, {
            opacity: this.state.scrollY.interpolate({
                inputRange: [agendaHeight - HEADER_HEIGHT, agendaHeight],
                outputRange: [0, 1],
                extrapolate: 'clamp',
            }),
            transform: [{ translateY: this.state.scrollY.interpolate({
                    inputRange: [Math.max(0, agendaHeight - HEADER_HEIGHT), agendaHeight],
                    outputRange: [-HEADER_HEIGHT, 0],
                    extrapolate: 'clamp',
                })}]
        }];

        const headerTranslate = this.state.scrollY.interpolate({
            inputRange: [0, agendaHeight],
            outputRange: [agendaHeight, 0],
            extrapolate: 'clamp',
        });

        const contentTranslate = this.state.scrollY.interpolate({
            inputRange: [0, agendaHeight],
            outputRange: [0, agendaHeight/2],
            extrapolate: 'clamp',
        });

        const headerStyle = [
            this.styles.header,
            { bottom: agendaHeight, transform: [{ translateY: headerTranslate }] },
        ];

        if (!this.state.calendarIsReady) {
            // limit header height until everything is setup for calendar dragging
            headerStyle.push({height: 0});
            // fill header with appStyle.calendarBackground background to reduce flickering
            weekdaysStyle.push({height: HEADER_HEIGHT});
        }

        const shouldAllowDragging = !this.props.hideKnob && !this.state.calendarScrollable;
        const scrollPadPosition = (shouldAllowDragging ? HEADER_HEIGHT  : 0) - KNOB_HEIGHT;

        const scrollPadStyle = {
            position: 'absolute',
            width: 80,
            height: KNOB_HEIGHT,
            top: scrollPadPosition,
            left: (this.viewWidth - 80) / 2,
        };

        let knob = (<View style={this.styles.knobContainer}/>);

        if (!this.props.hideKnob) {
            const knobView = this.props.renderKnob ? this.props.renderKnob() : (<View style={this.styles.knob}/>);
            knob = this.state.calendarScrollable ? null : (
                <View style={this.styles.knobContainer}>
                    <View ref={(c) => this.knob = c}>{knobView}</View>
                </View>
            );
        }
        return (
            <View onLayout={this.onLayout} style={[this.props.style, {flex: 1, overflow: 'hidden'}]}>
                <View style={this.styles.reservations}>
                    {this.renderReservations()}
                </View>
                <Animated.View style={headerStyle}>
                    <Animated.View style={{flex:1, transform: [{ translateY: contentTranslate }]}}>
                        <CalendarList
                            onLayout={() => {
                                this.calendar.scrollToDay(this.state.selectedDay.clone(), this.calendarOffset(), false);
                            }}
                            calendarWidth={this.viewWidth}
                            theme={this.props.theme}
                            onVisibleMonthsChange={this.onVisibleMonthsChange.bind(this)}
                            ref={(c) => this.calendar = c}
                            minDate={this.props.minDate}
                            maxDate={this.props.maxDate}
                            current={this.currentMonth}
                            markedDates={this.generateMarkings()}
                            markingType={this.props.markingType}
                            removeClippedSubviews={this.props.removeClippedSubviews}
                            onDayPress={this._chooseDayFromCalendar.bind(this)}
                            scrollingEnabled={this.state.calendarScrollable}
                            hideExtraDays={this.state.calendarScrollable}
                            firstDay={this.props.firstDay}
                            monthFormat={this.props.monthFormat}
                            pastScrollRange={this.props.pastScrollRange}
                            futureScrollRange={this.props.futureScrollRange}
                            dayComponent={this.props.dayComponent}
                            disabledByDefault={this.props.disabledByDefault}
                            displayLoadingIndicator={this.props.displayLoadingIndicator}
                            showWeekNumbers={this.props.showWeekNumbers}
                        />
                        <TouchableOpacity onPress={()=>{
                            this.closeCalendar();
                        }}>
                        <View style={{height: (this.isIphoneX()) ? 75 : 60,alignItems: 'center',justifyContent: 'center',backgroundColor: 'transparent'}}>

                                <View style={{width: Dimensions.get('window').width,justifyContent: 'center',alignItems: 'center'}}>
                                    {this.props.renderKnobUp()}
                                </View>
                        </View>
                        </TouchableOpacity>
                    </Animated.View>
                    {knob}
                </Animated.View>
                <Animated.View style={weekdaysStyle}>
                    {this.props.showWeekNumbers && <Text allowFontScaling={false} style={this.styles.weekday} numberOfLines={1}></Text>}
                    {weekDaysNames.map((day, index) => (
                        <Text allowFontScaling={false} key={day+index} style={this.styles.weekday} numberOfLines={1}>{day}</Text>
                    ))}
                </Animated.View>
                <Animated.ScrollView
                    ref={c => this.scrollPad = c}
                    overScrollMode='never'
                    showsHorizontalScrollIndicator={false}
                    showsVerticalScrollIndicator={false}
                    style={scrollPadStyle}
                    scrollEventThrottle={1}
                    scrollsToTop={false}
                    onTouchStart={this.onTouchStart}
                    onTouchEnd={this.onTouchEnd}
                    onScrollBeginDrag={this.onStartDrag}
                    onScrollEndDrag={this.onSnapAfterDrag}
                    onScroll={Animated.event(
                        [{ nativeEvent: { contentOffset: { y: this.state.scrollY } } }],
                        { useNativeDriver: true },
                    )}
                >
                    <View style={{height: agendaHeight + KNOB_HEIGHT}} onLayout={this.onScrollPadLayout} />
                </Animated.ScrollView>


            </View>
        );
    }
}
